/**
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY. 
 * See the GNU General Public License for more details.
 * 
 * Copyright (C) 2011 CellObject.net
 */
package net.cellobject.javacodeformatter.model;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import net.cellobject.javacodeformatter.FormatterPlugin;
import net.cellobject.javacodeformatter.log.FormatterLog;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.XMLMemento;

public class FormatterManager implements IResourceChangeListener {
	/**
	 * Flag: Don't listen to file changed event if it's currently in format mode, save action will cause eclipse to fire resource
	 * changed event.
	 */
	public static boolean isInFormatMode = false;

	private static final String TAG_FAVORITES = "Favorites";

	private static final String TAG_FAVORITE = "Favorite";

	private static final String TAG_TYPEID = "TypeId";

	private static final String TAG_INFO = "Info";

	private static FormatterManager manager;

	private Collection<IFormatterItem> favorites;

	private final List<FormatterManagerListener> listeners = new ArrayList<FormatterManagerListener>();

	private FormatterManager() {
		ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
	}

	// /////////////////////////////////////////////////////////////////////////
	//
	// Singleton
	//
	// /////////////////////////////////////////////////////////////////////////
	public static FormatterManager getManager() {
		if (manager == null) {
			manager = new FormatterManager();
		}
		return manager;
	}

	public static void shutdown() {
		if (manager != null) {
			ResourcesPlugin.getWorkspace().removeResourceChangeListener(manager);
			manager.saveFavorites();
			manager = null;
		}
	}

	// /////////////////////////////////////////////////////////////////////////
	//
	// Accessing files
	//
	// /////////////////////////////////////////////////////////////////////////
	public IFormatterItem[] getFavorites() {
		if (favorites == null) {
			loadFavorites();
		}
		return favorites.toArray(new IFormatterItem[favorites.size()]);
	}

	public void addFavorites(final Object[] objects) {
		if (objects == null) {
			return;
		}
		if (favorites == null) {
			loadFavorites();
		}
		final Collection<IFormatterItem> items = new HashSet<IFormatterItem>(objects.length);
		for (final Object object : objects) {
			IFormatterItem item = existingFavoriteFor(object);
			if (item == null) {
				item = newFavoriteFor(object);
				if (favorites.add(item)) {
					items.add(item);
				}
			}
		}
		if (items.size() > 0) {
			final IFormatterItem[] added = items.toArray(new IFormatterItem[items.size()]);
			fireFavoritesChanged(added, IFormatterItem.NONE);
		}
	}

	public void removeFavorites(final Object[] objects) {
		if (objects == null) {
			return;
		}
		if (favorites == null) {
			loadFavorites();
		}
		final Collection<IFormatterItem> items = new HashSet<IFormatterItem>(objects.length);
		for (final Object object : objects) {
			final IFormatterItem item = existingFavoriteFor(object);
			if ( (item != null) && favorites.remove(item)) {
				items.add(item);
			}
		}
		if (items.size() > 0) {
			final IFormatterItem[] removed = items.toArray(new IFormatterItem[items.size()]);
			fireFavoritesChanged(IFormatterItem.NONE, removed);
		}
	}

	public IFormatterItem newFavoriteFor(final Object obj) {
		final FormatterItemType[] types = FormatterItemType.getTypes();
		for (final FormatterItemType type : types) {
			final IFormatterItem item = type.newFavorite(obj);
			if (item != null) {
				return item;
			}
		}
		return null;
	}

	private IFormatterItem existingFavoriteFor(final Object obj) {
		if (obj == null) {
			return null;
		}
		if (obj instanceof IFormatterItem) {
			return (IFormatterItem)obj;
		}
		final Iterator<IFormatterItem> iter = favorites.iterator();
		while (iter.hasNext()) {
			final IFormatterItem item = iter.next();
			final IResource resource = (IResource)obj;
			//			if (item.isFavoriteFor(obj)) {
			//				return item;
			//			}
			if (resource.getName().equals(item.getName())) {
				return item;
			}
		}
		return null;
	}

	public IFormatterItem[] existingFavoritesFor(final Iterator<?> iter) {
		final List<IFormatterItem> result = new ArrayList<IFormatterItem>(10);
		while (iter.hasNext()) {
			final IFormatterItem item = existingFavoriteFor(iter.next());
			if (item != null) {
				result.add(item);
			}
		}
		return result.toArray(new IFormatterItem[result.size()]);
	}

	// /////////////////////////////////////////////////////////////////////////
	//
	// Event Handling
	//
	// /////////////////////////////////////////////////////////////////////////
	public void addFavoritesManagerListener(final FormatterManagerListener listener) {
		if (!listeners.contains(listener)) {
			listeners.add(listener);
		}
	}

	public void removeFavoritesManagerListener(final FormatterManagerListener listener) {
		listeners.remove(listener);
	}

	private void fireFavoritesChanged(final IFormatterItem[] itemsAdded, final IFormatterItem[] itemsRemoved) {
		final FormatterManagerEvent event = new FormatterManagerEvent(this, itemsAdded, itemsRemoved);
		for (final FormatterManagerListener formatterManagerListener : listeners) {
			formatterManagerListener.favoritesChanged(event);
		}
	}

	// /////////////////////////////////////////////////////////////////////////
	//
	// Persisting favorites
	//
	// /////////////////////////////////////////////////////////////////////////
	private void loadFavorites() {
		favorites = new HashSet<IFormatterItem>(20);
		FileReader reader = null;
		try {
			reader = new FileReader(getFavoritesFile());
			loadFavorites(XMLMemento.createReadRoot(reader));
		} catch (final FileNotFoundException e) {
			// Ignored... no Favorites items exist yet.
		} catch (final Exception e) {
			// Log the exception and move on.
			FormatterLog.logError(e);
		} finally {
			try {
				if (reader != null) {
					reader.close();
				}
			} catch (final IOException e) {
				FormatterLog.logError(e);
			}
		}
	}

	private void loadFavorites(final XMLMemento memento) {
		final IMemento[] children = memento.getChildren(TAG_FAVORITE);
		for (final IMemento element : children) {
			final IFormatterItem item = newFavoriteFor(element.getString(TAG_TYPEID), element.getString(TAG_INFO));
			if (item != null) {
				favorites.add(item);
			}
		}
	}

	public IFormatterItem newFavoriteFor(final String typeId, final String info) {
		final FormatterItemType[] types = FormatterItemType.getTypes();
		for (final FormatterItemType type : types) {
			if (type.getId().equals(typeId)) {
				return type.loadFavorite(info);
			}
		}
		return null;
	}

	public void saveFavorites() {
		if (favorites == null) {
			return;
		}
		final XMLMemento memento = XMLMemento.createWriteRoot(TAG_FAVORITES);
		saveFavorites(memento);
		FileWriter writer = null;
		try {
			writer = new FileWriter(getFavoritesFile());
			memento.save(writer);
		} catch (final IOException e) {
			FormatterLog.logError(e);
		} finally {
			try {
				if (writer != null) {
					writer.close();
				}
			} catch (final IOException e) {
				FormatterLog.logError(e);
			}
		}
	}

	private void saveFavorites(final XMLMemento memento) {
		final Iterator<IFormatterItem> iter = favorites.iterator();
		while (iter.hasNext()) {
			final IFormatterItem item = iter.next();
			final IMemento child = memento.createChild(TAG_FAVORITE);
			child.putString(TAG_TYPEID, item.getType().getId());
			child.putString(TAG_INFO, item.getInfo());
		}
	}

	private File getFavoritesFile() {
		return FormatterPlugin.getDefault().getStateLocation().append("favorites.xml").toFile();
	}

	////////////////////////////////////////////////////////////////////////////
	//
	// Resource change event handling
	//
	////////////////////////////////////////////////////////////////////////////
	/**
	 * Called by the system when some resource changes are happening, or have already happened. The supplied event gives details.
	 * This event object (and the resource delta within it) is valid only for the duration of the invocation of this method.
	 */
	public void resourceChanged(final IResourceChangeEvent event) {
		final Collection<IFormatterItem> itemsToRemove = new HashSet<IFormatterItem>();
		final Collection<IResource> itemsToAdd = new HashSet<IResource>();
		try {
			event.getDelta().accept(new IResourceDeltaVisitor() {
				public boolean visit(final IResourceDelta delta) throws CoreException {
					switch (delta.getKind()) {
						case IResourceDelta.ADDED:
							//This will also add all java files to the list when the project is opened.
							//When creating a new java file, don't need to add it to the formatter list
							//because soon or later we will modify it, and it will be added to the formatter list when modifying.
							//if ( (delta.getResource() instanceof IFile) && delta.getResource().toString().endsWith(".java")) {
							//itemsToAdd.add(delta.getResource());
							//}
							break;
						case IResourceDelta.REMOVED:
							final IFormatterItem itemToRemove = existingFavoriteFor(delta.getResource());
							if (itemToRemove != null) {
								itemsToRemove.add(itemToRemove);
							}
							break;
						case IResourceDelta.CHANGED:
							if ( (delta.getResource() instanceof IFile)
									&& (delta.getResource().toString().endsWith(".java") )) {
								if (!isInFormatMode && ( (delta.getFlags() & IResourceDelta.CONTENT) != 0)) {
									if ( ( (delta.getFlags() & IResourceDelta.SYNC) == 0)) {
										//Don't catch changes triggered from building to test-classes
										if (!delta.getResource().toString().contains("test-classes") && !delta.getResource().toString().contains("classes")) {
											itemsToAdd.add(delta.getResource());
										}
									} else if ( ( (delta.getFlags() & IResourceDelta.REPLACED) == 0)) {
										final IFormatterItem itemToChange = existingFavoriteFor(delta.getResource());
										if (itemToChange != null) {
											itemsToRemove.add(itemToChange);
										}
									}
								}
							}
							break;
						default:
							break;
					}
					return true;
				}
			});
		} catch (final CoreException ex) {
			FormatterLog.logError(ex);
			ex.printStackTrace();
		}
		if (itemsToAdd.size() > 0) {
			addFavorites(itemsToAdd.toArray());
		}
		if (itemsToRemove.size() > 0) {
			removeFavorites(itemsToRemove.toArray(new IFormatterItem[itemsToRemove.size()]));
		}
	}
}
